# Copyright 2018 Maco
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
import json
import logging
import functools
from concurrent.futures import ThreadPoolExecutor
import tornado.web
from tornado.ioloop import IOLoop

from blockchain.config import config
from blockchain.common import constants
from blockchain import version


_DEF_ARG = object()
LOG = logging.getLogger('base')

# server name
SERVER_NAME = config.get_server_name() + '/' + version

# worker threads, type int
_WORKER_THREADS = config.get_int_opt('base', '_worker_threads', 8)

# thread pool
EXECUTOR = ThreadPoolExecutor(max_workers=_WORKER_THREADS)


class Message(object):
    '''Return message classes.'''

    def __init__(self, code=constants.CODE_SUCCESS,
                 message=None, data=None, url=None):
        self.code = code
        self.message = message
        self.data = data
        self.url = url

    def get_message(self):
        return {
            'code': self.code,
            'message': self.message,
            'data': self.data
        }


def success(message=None, data=None, url=None):
    '''Return success message.'''
    return Message(constants.CODE_SUCCESS, message, data, url)


def failure(message=None, data=None, url=None):
    '''Return failure message.'''
    return Message(constants.CODE_FAILED, message, data, url)


class BaseHandler(tornado.web.RequestHandler):
    url_pattern = None

    def prepare(self):
        '''
        Process system parameter setting.
        Cannot override this method, but you can override self.request_prepare() method.
        '''
        self.set_header('Server', SERVER_NAME)

    def get_query(self, name='query'):
        '''Return query value if has else return None.'''
        return self.get_arg(name, None)

    def lang(self, message):
        return self.locale.translate(message)

    def get_arg(self, name, default=_DEF_ARG, strip=True):
        value = self.get_argument(name, default=default, strip=strip)
        return value

    def get_int_arg(self, name, default=0):
        '''Return int value.'''
        val = self.get_arg(name, default, False)
        if isinstance(val, int):
            return val
        try:
            val = int(val)
        except ValueError:
            val = default
        return val

    def get_args(self, name, strip=True):
        return self.get_arguments(name, strip)

    def get_body_arg(self, name, default=_DEF_ARG, strip=True):
        return self.get_body_argument(name, default=default, strip=strip)

    def get_body_args(self, name, strip=True):
        return self.get_body_arguments(name, strip)

    def get_request_body(self, loads=True, default={}):
        '''
        post from submit json data.
        '''
        if loads:
            try:
                return json.loads(self.request.body)
            except json.decoder.JSONDecodeError:
                return default
        return self.request.body or default

    def get(self):
        '''Default return 404 error.'''
        self.set_status(404)
        self.write_error(self.get_status())

    def set_json_header(self):
        self.set_header('Content-Type', 'application/json; charset=UTF-8')

    def success(self, message=None, data=None, url=None):
        '''
        Write success message to browser.
        '''
        self.process(constants.CODE_SUCCESS, message, data, url)

    def failure(self, message=None, data=None, url=None):
        '''
        Write failure message to browser.
        '''
        self.process(constants.CODE_FAILED, message, data, url)

    def write_error(self, status_code, **kwargs):
        '''
        Return error code.
        such as: 400 401 403 404 405 500
        Not login error code: 10001
        '''
        self.set_header('Server', SERVER_NAME)
        _code = constants.CODE_FAILED
        message = self.lang('Request error code: %d') % status_code
        self.process(_code, message, None, 'error.html')

    def process(self, code, message, data=None, url=None):
        '''
        Process server write json data to client.
        '''
        _dict = {'code': code, 'message': message, 'data': data}
        if url:
            self.render(url, error=message)
        else:
            self.set_json_header()
            self.write(json.dumps(_dict))


class ErrorHandler(BaseHandler):
    '''
    Tornado default error handler, can not delete.
    '''

    def get(self):
        self.failure('Bad request.')


class FrontendHandler(BaseHandler):
    '''
    Frontend handler inherit class 
    '''
    # url prefix
    url_prefix = '/api'


def route(url_pattern):
    '''
    e.g:
    @route(r'/test')
    class TestHandler(BaseHandler):
        def get(self):
            self.write('Hello world.')
    '''
    def warpper(cls):
        cls.url_pattern = url_pattern
        return cls
    return warpper


def noblock(func):
    '''
    async thread
    e.g:
    class TestHandler(BaseHandler):

        @noblock
        def get(self):
            return success(data='test')
            #if used self.render('index.html', data='')
            #return success(data='test', url='index.html')
    '''
    @tornado.web.asynchronous
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        self = args[0]

        def callback(result):
            '''Callback response result.'''
            if not self._finished:
                if result:
                    if result.url:
                        self.render('index.html', data=result.data)
                    else:
                        self.set_json_header()
                        self.write(json.dumps(result.get_message()))
                else:
                    self.write_error(400)
            else:
                self.write(result)
            if not self._finished:
                self.finish()

        future = EXECUTOR.submit(func, *args, **kwargs)
        IOLoop.current().add_future(future, lambda future: callback(future.result()))
    return wrapper
